#include"header/compiler_io.h"
#include"header/token.h"
#include"header/scanner.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

enum SCANNER_STATE{
    SCAN_START,
    SCAN_DIGIT,
    SCAN_LETTER,
    SCAN_OP,
    SCAN_IS_ANNOTATION,
    SCAN_ANNOTATION,
    SCAN_END
};

char scanner_state = SCAN_START;
char scanner_chr = 0;

struct{
    int lines;
    int words;
}text_info = {1,0};

int current_char(){
    return scanner_chr;
}

int forward_char(){
    scanner_chr = compiler_read_char();
    if(scanner_chr == '\n'){
        text_info.lines++;
        text_info.words = 0;
    }else{
        text_info.words++;
    }
    return scanner_chr;
}

void log_error(){
    printf("\033[31mfatal error\033[0m: at \033[1mlines:\033[0m %d, \033[1mwords:\033[0m %d\n",
                text_info.lines,text_info.words);
    exit(0);
}

int current_state(){
    return scanner_state;
}

int set_state(int state){
    scanner_state = state;
    return scanner_state;
}

int is_alpha(char c){
    if ((c>='a' && c<='z') || (c>='A' && c<='Z'))
        return 1;
    else
        return 0;
}

int is_digit(char c){
    if (c>='0' && c<='9')
        return 1;
    else
        return 0;
}

int is_blank(char c){
    if(c == '\t' || c=='\r' || c =='\n' || c ==' ' || c == 0)
        return 1;
    else
        return 0;
}

int buf_to_digit(char *buf){
    int digit = 0;
    int idx = 0;
    while(buf[idx] != 0)
        digit = digit*10 + buf[idx++] - '0';
    
    return digit;
}


char * expand_buf(char * buf,int size,int expand_size){
    char * tmp = (char *)calloc(size+expand_size,sizeof(char));
    memcpy(tmp,buf,size);
    return tmp;
}

token token_scan(){
    int type = TOKEN_UNKOWN;
    int annotation = 0;
    token tk = init_token(type,NULL,0);
    int buf_idx = 0;
    int size = SCANNER_DEFAULT_LETTER_BUF;
    char * buf = (char *)calloc(SCANNER_DEFAULT_LETTER_BUF,sizeof(char));

    set_state(SCAN_START);

    while(current_state() != SCAN_END){
        switch (current_state()){
        case SCAN_START:
            if(current_char()==EOF){
                type = TOKEN_EOF;
                set_state(SCAN_END);
            }
            else if(is_digit(current_char()))
                set_state(SCAN_DIGIT);
            else if(is_alpha(current_char()))
                set_state(SCAN_LETTER);
            else if(!is_blank(current_char()))
                set_state(SCAN_OP);
            else{
                forward_char();
            }
            break;
        case SCAN_OP:
            switch(current_char()){
                case '.':
                    type = TOKEN_DOT;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '+':
                    type = TOKEN_ADD;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '-':
                    type = TOKEN_MINUS;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '*':
                    type = TOKEN_STAR;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '/':
                    type = TOKEN_DIV;
                    forward_char();
                    set_state(SCAN_IS_ANNOTATION);
                    break;
                case '=':
                    type = TOKEN_ASSIGN;
                    forward_char();
                    if(current_char()=='='){
                        type=TOKEN_EQUAL;
                        forward_char();
                    }
                    set_state(SCAN_END);
                    break;
                case '>':
                    type = TOKEN_GREAT;
                    forward_char();
                    if(current_char()=='='){
                        type=TOKEN_GREAT_EQUAL;
                        forward_char();
                    }
                    set_state(SCAN_END);
                    break;
                case '<':
                    type = TOKEN_LESS;
                    forward_char();
                    if(current_char()=='='){
                        type=TOKEN_LESS_EQUAL;
                        forward_char();
                    }
                    set_state(SCAN_END);
                    break;
                case '!':
                    type = TOKEN_NOT;
                    forward_char();
                    if(current_char()=='='){
                        type=TOKEN_NOT_EQUAL;
                        forward_char();
                    }
                    set_state(SCAN_END);
                    break;
                case ';':
                    type = TOKEN_SEMICOLON;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case ',':
                    type = TOKEN_COMMA;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '(':
                    type = TOKEN_LEFT_BRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case ')':
                    type = TOKEN_RIGHT_BRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '[':
                    type = TOKEN_LEFT_SBRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case ']':
                    type = TOKEN_RIGHT_SBRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '{':
                    type = TOKEN_LEFT_CBRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                case '}':
                    type = TOKEN_RIGHT_CBRACE;
                    forward_char();
                    set_state(SCAN_END);
                    break;
                default:
                    type = TOKEN_UNKOWN;
                    forward_char();
                    set_state(SCAN_END);
                    break;
            }
            break;
        case SCAN_IS_ANNOTATION:
            if(current_char()=='*'){
                forward_char();
                set_state(SCAN_ANNOTATION);
            }else{
                set_state(SCAN_END);
            }
            break;
        case SCAN_ANNOTATION:
            while(1){
                while(current_char() != '*'){
                    if(current_char() == EOF)
                        break;
                    if(current_char() == '/'){
                        forward_char();
                        if(current_char() == '*'){
                            forward_char();
                            annotation++;
                            continue;
                        }
                    }
                    forward_char();
                }
                if(current_char() == EOF){
                    type=TOKEN_EOF;
                    set_state(SCAN_END);
                    break;
                }
                forward_char();
                if(current_char() == '/'){
                    if(annotation == 0){
                        forward_char();
                        set_state(SCAN_START);
                        break;
                    }else{
                        annotation--;
                        forward_char();
                    }
                }
            }
            break;
        case SCAN_DIGIT:
            while(is_digit(current_char())){
                if(buf_idx >= size-1)
                    buf = expand_buf(buf,size,SCANNER_DEFAULT_LETTER_BUF);
                buf[buf_idx++] = current_char();
                forward_char();
            }
            buf[buf_idx++] = 0;
            type = TOKEN_NUM;
            set_state(SCAN_END);
            break;
        case SCAN_LETTER:
            while(is_alpha(current_char())){
                if(buf_idx >= size-1)
                    buf = expand_buf(buf,size,SCANNER_DEFAULT_LETTER_BUF);
                buf[buf_idx++] = current_char();
                forward_char();
            }
            buf[buf_idx++] = 0;
            if(!strcmp("if",buf))
                type=TOKEN_IF;
            else if(!strcmp("else",buf))
                type=TOKEN_ELSE;
            else if(!strcmp("switch",buf))
                type=TOKEN_SWITCH;
            else if(!strcmp("case",buf))
                type=TOKEN_CASE;
            else if(!strcmp("default",buf))
                type=TOKEN_DEFAULT;
            else if(!strcmp("int",buf))
                type=TOKEN_INT;
            else if(!strcmp("void",buf))
                type=TOKEN_VOID;
            else if(!strcmp("struct",buf))
                type=TOKEN_STRUCT;
            else if(!strcmp("return",buf))
                type=TOKEN_RETURN;
            else if(!strcmp("break",buf))
                type=TOKEN_BREAK;
            else if(!strcmp("goto",buf))
                type=TOKEN_GOTO;
            else if(!strcmp("while",buf))
                type=TOKEN_WHILE;
            else if(!strcmp("for",buf))
                type=TOKEN_FOR;
            else
                type=TOKEN_ID;
            set_state(SCAN_END);
            break;
        default:
            break;
        }
    }
    
    switch(type){
        case TOKEN_NUM:
            set_token_type(tk,type);
            set_token_digit(tk,buf_to_digit(buf));
            break;
        case TOKEN_ID:
            set_token_type(tk,type);
            set_token_letter(tk,buf);
            break;
        default:
            set_token_type(tk,type);
            free(buf);
            break;
    }
    return tk;
}

